package com.atmosphere.component;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.atmosphere.component.base.ClocktimeComponent;
import com.atmosphere.interfaces.IAtmosphere;
import com.atmosphere.interfaces.IComponentKey;
import com.atmosphere.interfaces.component.IFogComponent;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.state.FogState;
import com.jme.scene.state.FogState.DensityFunction;
import com.jme.system.DisplaySystem;

/**
 * <code>FogComponent</code> defines the implementation of the
 * component unit that is responsible for providing the fog
 * effect to the atmosphere system.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 05-21-2009 17:11 EST
 * @version Modified date: 05-21-2009 19:46 EST
 */
public class FogComponent extends ClocktimeComponent implements IFogComponent {
	/**
	 * The <code>FogState</code>.
	 */
	private final FogState state;
	/**
	 * The update <code>Lock</code>.
	 */
	private final Lock lock;
	/**
	 * The <code>Float</code> array of day-time fog color.
	 */
	private final float[] day;
	/**
	 * The <code>Float</code> array of night-time fog color.
	 */
	private final float[] night;
	/**
	 * The <code>Float</code> array of transition-time fog color.
	 */
	private final float[] transition;
	/**
	 * The temporary <code>Float</code> array of fog color.
	 */
	private final float[] tempColor;
	/**
	 * The <code>Float</code> start distance.
	 */
	private volatile float start;
	/**
	 * The <code>Float</code> end distance.
	 */
	private volatile float end;
	/**
	 * The <code>Float</code> fog density.
	 */
	private volatile float density;
	/**
	 * The <code>Boolean</code> value dirty flag.
	 */
	private volatile boolean valueDirty;
	/**
	 * The <code>IAtmosphere</code> system.
	 */
	private IAtmosphere atmosphere;

	/**
	 * Constructor of <code>FogComponent</code>.
	 * @param key The <code>IComponentKey</code>.
	 */
	public FogComponent(IComponentKey key) {
		this(key, DisplaySystem.getDisplaySystem().getRenderer().createFogState());
	}
	
	/**
	 * Constructor of <code>FogComponent</code>.
	 * @param key The <code>IComponentKey</code>.
	 * @param state The <code>FogState</code> to be used.
	 */
	public FogComponent(IComponentKey key, FogState state) {
		super(key);
		this.state = state;
		this.lock = new ReentrantLock();
		this.day = new float[4];
		this.night = new float[4];
		this.transition = new float[4];
		this.tempColor = new float[4];
		// Default values.
		final Camera camera = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
		this.density = 0.5f;
		this.start = camera.getFrustumFar()*0.2f;
		this.end = camera.getFrustumFar();
	}

	@Override
	public void initialize(Node parent, IAtmosphere atmosphere) {
		this.atmosphere = atmosphere;
		this.initFogState();
	}
	
	/**
	 * Initialize the fog state.
	 */
	private void initFogState() {
		this.state.setDensity(this.density);
		this.state.setDensityFunction(FogState.DensityFunction.Linear);
		this.state.setQuality(FogState.Quality.PerPixel);
		this.state.setStart(this.start);
		this.state.setEnd(this.end);
		this.state.setEnabled(true);
		this.valueDirty = false;
	}

	@Override
	protected void update(float clocktime, float interpolation) {
		// Check dirty.
		if(this.valueDirty) this.initFogState();
		this.lock.lock();
		try {
			this.updateColor(clocktime);
		} finally {
			this.lock.unlock();
		}
	}

	/**
	 * Update the fog color based on given time.
	 * @param clocktime The <code>Float</code> clock-time.
	 */
	private void updateColor(float clocktime) {
		// Day time.
		if(clocktime >= this.atmosphere.getDayStart() && clocktime < this.atmosphere.getDayEnd()) {
			for(int i = 0; i < this.tempColor.length; i++) this.tempColor[i] = this.day[i];
			this.setFogColor();
		// Else if the time is between day-end and day-transition, change towards transition color.
		} else if(clocktime >= this.atmosphere.getDayEnd() && clocktime < this.atmosphere.getDayTransition()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.day[i] +
					(this.transition[i]-this.day[i])*(clocktime-this.atmosphere.getDayEnd())/this.atmosphere.getDiffTransDay();
			}
			this.setFogColor();
		// Else if the time is between day-transition and night-start, change towards night color.
		} else if(clocktime >= this.atmosphere.getDayTransition() && clocktime < this.atmosphere.getNightStart()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.transition[i] +
					(this.night[i]-this.transition[i])*(clocktime-this.atmosphere.getDayTransition())/this.atmosphere.getDiffNightTrans();
			}
			this.setFogColor();
		// Night time.
		} else if(clocktime >= this.atmosphere.getNightStart() || clocktime < this.atmosphere.getNightEnd()) {
			for(int i = 0; i < this.tempColor.length; i++) this.tempColor[i] = this.night[i];
			this.setFogColor();
		// Else if the time is between night-end and night-transition, change towards transition color.
		} else if(clocktime >= this.atmosphere.getNightEnd() && clocktime < this.atmosphere.getNightTransition()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.night[i] +
					(this.transition[i]-this.night[i])*(clocktime-this.atmosphere.getNightEnd())/this.atmosphere.getDiffTransNight();
			}
			this.setFogColor();
		// If the time is between night-transition and day-start, change towards day color.
		} else if(clocktime >= this.atmosphere.getNightTransition() && clocktime < this.atmosphere.getDayStart()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.transition[i] +
					(this.day[i]-this.transition[i])*(clocktime-this.atmosphere.getNightTransition())/this.atmosphere.getDiffDayTrans();
			}
			this.setFogColor();
		}
	}
	
	/**
	 * Set the fog color to the temporary color.
	 */
	private void setFogColor() {
		this.state.getColor().r = this.tempColor[0];
		this.state.getColor().g = this.tempColor[1];
		this.state.getColor().b = this.tempColor[2];
		this.state.getColor().a = this.tempColor[3];
		this.state.getColor().clamp();
	}

	@Override
	public void setEnd(float distance) {
		this.end = distance;
		this.valueDirty = true;
	}

	@Override
	public void setStart(float distance) {
		this.start = distance;
		this.valueDirty = true;
	}

	@Override
	public void setDensity(float density) {
		this.density = density;
		this.valueDirty = true;
	}

	@Override
	public void setDayColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.day[0] = color.r;
			this.day[1] = color.g;
			this.day[2] = color.b;
			this.day[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setNightColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.night[0] = color.r;
			this.night[1] = color.g;
			this.night[2] = color.b;
			this.night[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setTransitionColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.transition[0] = color.r;
			this.transition[1] = color.g;
			this.transition[2] = color.b;
			this.transition[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setFunction(DensityFunction function) {
		this.state.setDensityFunction(function);
	}

	@Override
	public FogState getFogState() {
		return this.state;
	}

	@Override
	public float getEnd() {
		return this.end;
	}

	@Override
	public float getStart() {
		return this.start;
	}

	@Override
	public float getDensity() {
		return this.density;
	}

	@Override
	public ColorRGBA getDayColor() {
		return new ColorRGBA(this.day[0], this.day[1], this.day[2], this.day[3]);
	}

	@Override
	public ColorRGBA getNightColor() {
		return new ColorRGBA(this.night[0], this.night[1], this.night[2], this.night[3]);
	}

	@Override
	public ColorRGBA getTransitionColor() {
		return new ColorRGBA(this.transition[0], this.transition[1], this.transition[2], this.transition[3]);
	}

	@Override
	public void cleanup() {}
}
